package com.zeyu.framework.modules.common.openid.provider;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.brickred.socialauth.AbstractProvider;
import org.brickred.socialauth.AuthProvider;
import org.brickred.socialauth.Contact;
import org.brickred.socialauth.Permission;
import org.brickred.socialauth.Profile;
import org.brickred.socialauth.exception.AccessTokenExpireException;
import org.brickred.socialauth.exception.SocialAuthException;
import org.brickred.socialauth.exception.UserDeniedPermissionException;
import org.brickred.socialauth.oauthstrategy.OAuth2;
import org.brickred.socialauth.oauthstrategy.OAuthStrategyBase;
import org.brickred.socialauth.util.AccessGrant;
import org.brickred.socialauth.util.Constants;
import org.brickred.socialauth.util.MethodType;
import org.brickred.socialauth.util.OAuthConfig;
import org.brickred.socialauth.util.Response;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * QQ第三方登录服务提供者
 * Created by zeyuphoenix on 2017/1/5.
 */
public class QQAuthProvider extends AbstractProvider implements AuthProvider, Serializable {

    // ================================================================
    // Constants
    // ================================================================

    /**
     * uid
     */
    private static final long serialVersionUID = 1L;
    /**
     * logger
     */
    private static final Logger logger = LoggerFactory.getLogger(QQAuthProvider.class);

    // auth url
    private static final String PROFILE_URL = "https://graph.qq.com/user/get_info";
    private static final String OPENID_URL = "https://graph.qq.com/oauth2.0/me";

    // set this to the list of extended permissions you want
    private static final String AllPerms = "user,public_repo,repo,gists,get_info,add_t";
    private static final String AuthenticateOnlyPerms = "user";

    // end point
    private static final Map<String, String> ENDPOINTS;

    static {
        ENDPOINTS = Maps.newHashMap();
        ENDPOINTS.put(Constants.OAUTH_AUTHORIZATION_URL,
                "https://graph.qq.com/oauth2.0/authorize");
        ENDPOINTS.put(Constants.OAUTH_ACCESS_TOKEN_URL,
                "https://graph.qq.com/oauth2.0/token");
    }

    // ================================================================
    // Fields
    // ================================================================

    // params
    private String openid;
    private Permission scope;
    private OAuthConfig config;
    private Profile userProfile;
    private AccessGrant accessGrant;
    private OAuthStrategyBase authenticationStrategy;

    // ================================================================
    // Constructors
    // ================================================================

    /**
     * Stores configuration for the provider
     *
     * @param providerConfig
     *            It contains the configuration of application like consumer key
     *            and consumer secret
     * @throws Exception
     */
    public QQAuthProvider(final OAuthConfig providerConfig) throws Exception {
        config = providerConfig;
        if (config.getCustomPermissions() != null) {
            this.scope = Permission.CUSTOM;
        }

        if (config.getAuthenticationUrl() != null) {
            ENDPOINTS.put(Constants.OAUTH_AUTHORIZATION_URL,
                    config.getAuthenticationUrl());
        } else {
            config.setAuthenticationUrl(ENDPOINTS
                    .get(Constants.OAUTH_AUTHORIZATION_URL));
        }

        if (config.getAccessTokenUrl() != null) {
            ENDPOINTS.put(Constants.OAUTH_ACCESS_TOKEN_URL,
                    config.getAccessTokenUrl());
        } else {
            config.setAccessTokenUrl(ENDPOINTS
                    .get(Constants.OAUTH_ACCESS_TOKEN_URL));
        }

        authenticationStrategy = new OAuth2(config, ENDPOINTS);
        authenticationStrategy.setPermission(scope);
        authenticationStrategy.setScope(getScope());
    }

    // ================================================================
    // Methods from/for super Interfaces or SuperClass
    // ================================================================

    @Override
    protected List<String> getPluginsList() {
        List<String> list = Lists.newArrayList();
        if (config.getRegisteredPlugins() != null
                && config.getRegisteredPlugins().length > 0) {
            list.addAll(Arrays.asList(config.getRegisteredPlugins()));
        }
        return list;
    }

    @Override
    protected OAuthStrategyBase getOauthStrategy() {
        return authenticationStrategy;
    }

    @Override
    public String getLoginRedirectURL(String successUrl) throws Exception {
        return authenticationStrategy.getLoginRedirectURL(successUrl);
    }

    @Override
    public Profile verifyResponse(Map<String, String> requestParams) throws Exception {
        return doVerifyResponse(requestParams);
    }

    @Override
    public Response updateStatus(String msg) throws Exception {
        CloseableHttpClient hc = HttpClientBuilder.create().build();
        try {
            HttpPost http_post = new HttpPost("https://graph.qq.com/t/add_t");

            List<NameValuePair> formparams = Lists.newArrayList();
            formparams.add(new BasicNameValuePair("openid", openid));
            formparams.add(new BasicNameValuePair("access_token", accessGrant
                    .getKey()));
            formparams.add(new BasicNameValuePair("oauth_consumer_key", config
                    .get_consumerKey()));
            formparams.add(new BasicNameValuePair("format", "json"));
            formparams.add(new BasicNameValuePair("content", msg));

            http_post.setEntity(new UrlEncodedFormEntity(formparams, "UTF-8"));
            hc.execute(http_post).getEntity();
        } catch (Exception e) {
            logger.error("update status error: ", e);
        } finally {
            try {
                hc.close();
            } catch (Exception ignored) {
            }
        }
        return null;
    }

    @Override
    public List<Contact> getContactList() throws Exception {
        logger.warn("WARNING: Not implemented for QQ");
        throw new SocialAuthException(
                "Get contact list is not implemented for QQ");
    }

    @Override
    public Profile getUserProfile() throws Exception {
        if (userProfile == null && accessGrant != null) {
            getProfile();
        }
        return userProfile;
    }

    @Override
    public void logout() {
        accessGrant = null;
        authenticationStrategy.logout();
    }

    @Override
    public void setPermission(Permission p) {
        logger.debug("Permission requested : " + p.toString());
        this.scope = p;
        authenticationStrategy.setPermission(this.scope);
        authenticationStrategy.setScope(getScope());
    }

    @Override
    public Response api(final String url, final String methodType,
                        final Map<String, String> params,
                        final Map<String, String> headerParams, final String body) throws Exception {
        logger.debug("Calling URL : " + url);
        Response serviceResponse;
        try {
            serviceResponse = authenticationStrategy.executeFeed(url,
                    methodType, params, headerParams, body);
        } catch (Exception e) {
            throw new SocialAuthException(
                    "Error while making request to URL : " + url, e);
        }
        if (serviceResponse.getStatus() != 200) {
            logger.debug("Return status for URL " + url + " is "
                    + serviceResponse.getStatus());
            throw new SocialAuthException("Error while making request to URL :"
                    + url + "Status : " + serviceResponse.getStatus());
        }
        return serviceResponse;
    }

    @Override
    public AccessGrant getAccessGrant() {
        return accessGrant;
    }

    @Override
    public String getProviderId() {
        return config.getId();
    }

    @Override
    public void setAccessGrant(AccessGrant accessGrant) throws AccessTokenExpireException, SocialAuthException {
        this.accessGrant = accessGrant;
        scope = accessGrant.getPermission();
        authenticationStrategy.setAccessGrant(accessGrant);
    }

    @Override
    public Response uploadImage(String s, String s1, InputStream inputStream) throws Exception {
        logger.warn("WARNING: Not implemented for QQ");
        throw new SocialAuthException(
                "Upload Image is not implemented for QQ");
    }

    // ================================================================
    // Public or Protected Methods
    // ================================================================

    // ================================================================
    // Getter & Setter
    // ================================================================

    // ================================================================
    // Private Methods
    // ================================================================

    private Profile getProfile() throws Exception {
        Profile p = new Profile();
        Response serviceResponse;
        // 先获取openid
        try {
            serviceResponse = authenticationStrategy.executeFeed(OPENID_URL
                    + "?format=json");
        } catch (Exception e) {
            throw new SocialAuthException(
                    "Failed to retrieve the user profile from  " + PROFILE_URL,
                    e);
        }
        String openid_result;
        try {
            openid_result = serviceResponse
                    .getResponseBodyAsString(Constants.ENCODING);
            logger.debug("User OPENID :" + openid_result);
        } catch (Exception e) {
            throw new SocialAuthException("Failed to read response from  "
                    + OPENID_URL, e);
        }
        JSONObject openid_resp = new JSONObject(openid_result.replace(
                "callback( ", "").replace(");", ""));
        if (openid_resp.has("openid"))
            this.openid = openid_resp.getString("openid");
        else
            throw new SocialAuthException("Failed to parse the openid json : "
                    + openid_result);

        // 再获取个人信息
        try {
            serviceResponse = authenticationStrategy.executeFeed(PROFILE_URL
                    + "?openid=" + openid + "&oauth_consumer_key="
                    + config.get_consumerKey());
        } catch (Exception e) {
            throw new SocialAuthException(
                    "Failed to retrieve the user profile from  " + PROFILE_URL,
                    e);
        }

        String result;
        try {
            result = serviceResponse
                    .getResponseBodyAsString(Constants.ENCODING);
            logger.debug("User Profile :" + result);
        } catch (Exception e) {
            throw new SocialAuthException("Failed to read response from  "
                    + PROFILE_URL, e);
        }
        try {
            JSONObject resp = new JSONObject(result);

            p.setValidatedId(openid);
            p.setEmail(openid);
            if (resp.has("data")) {
                JSONObject data = resp.getJSONObject("data");
                if (data.has("nick")) {
                    p.setFullName(data.getString("nick"));
                } else if (data.has("name")) {
                    p.setFullName(data.getString("name"));
                }
                if (data.has("sex")) {
                    if ("1".equals(data.getString("sex")))
                        p.setGender("male");
                    else
                        p.setGender("female");
                }
                if (data.has("head")) {
                    p.setProfileImageURL(data.getString("head") + "/100");
                }
            }
            serviceResponse.close();
            p.setProviderId(getProviderId());
            userProfile = p;
            return p;
        } catch (Exception e) {
            throw new SocialAuthException(
                    "Failed to parse the user profile json : " + result, e);
        }
    }

    private Profile doVerifyResponse(final Map<String, String> requestParams)
            throws Exception {

        logger.info("Retrieving Access Token in verify response function");
        if (requestParams.get("error_reason") != null
                && "user_denied".equals(requestParams.get("error_reason"))) {
            throw new UserDeniedPermissionException();
        }
        accessGrant = authenticationStrategy.verifyResponse(requestParams,
                MethodType.POST.toString());

        if (accessGrant != null) {
            logger.debug("Obtaining user profile");
            return getProfile();
        } else {
            throw new SocialAuthException("Access token not found");
        }
    }

    private String getScope() {
        String scopeStr;
        if (Permission.AUTHENTICATE_ONLY.equals(scope)) {
            scopeStr = AuthenticateOnlyPerms;
        } else if (Permission.CUSTOM.equals(scope)) {
            scopeStr = config.getCustomPermissions();
        } else {
            scopeStr = AllPerms;
        }
        return scopeStr;
    }

    // ================================================================
    // Inner or Anonymous Class
    // ================================================================

    // ================================================================
    // Test Methods
    // ================================================================

}
